Sending WCF messages being delayed under load - networking

When sending messages from a self hosted WCF service to many clients (about 10 or so), sometimes messages are being delayed significantly longer than I'd expect (several seconds to send to a client on local network). Does anyone have an idea why this would be and how to fix it?
Some background: the application is a stock ticker style service. It receives messages from a 3rd party server and re-publishes them to clients that connect to the service. It's very important that messages are published as quickly as possible, and in most cases the time between receiving a message and publishing it to all clients is less than 50ms (it's so quick it approaches the resolution of DateTime.Now).
Over the past few weeks, we've been monitoring some occasions when messages are delayed by 2 or 3 seconds. A few days ago, we got a big spike and messages were being delayed by 40-60 seconds. Messages are not being dropped as far as I can tell (unless the entire connection is dropped). The delays does not appear to be specific to any one client; it affects all clients (including ones on the local network).
I send messages to the clients by spamming the ThreadPool. As quickly as messages arrive I call BeginInvoke() once per message per client. The theory being that if any one client is slow to receive a message (because it's on dialup and downloading updates or something) that it won't impact other clients. That isn't what I'm observing though; it appears that all clients (including ones on the local network) are impacted by the delay by a similar duration.
The volume of messages I'm dealing with is 100-400 per second. Messages contain a string, a guid, a date and, depending on the message type, 10-30 integers. I've observed them using Wireshark as being less than 1kB each. We have 10-20 clients connected at any one time.
The WCF server is being hosted in a Windows service on a Windows 2003 Web Edition Server. I'm using the NetTCP binding with SSL/TLS encryption enabled and a custom username / password authentication. It has a 4Mbit internet connection, dual core CPU and 1GB ram and is dedicated to this application. The service is set to ConcurrencyMode.Multiple. The service process, even under high load, rarely exceeds 20% CPU usage.
So far, I've tweaked various WCF configuration options such as:
serviceBehaviors/serviceThrottling/maxConcurrentSessions (currently 102)
serviceBehaviors/serviceThrottling/maxConcurrentCalls (currently 64)
bindings/netTcpBinding/binding/maxConnections (currently 100)
bindings/netTcpBinding/binding/listenBacklog (currently 100)
bindings/netTcpBinding/binding/sendTimeout (currently 45s, although I've tried it as high as 3 minutes)
It appears to me like the messages are being queued inside WCF once some threshold is reached (hence why I've being increasing the throttling limits). But to affect all clients it would need to max out all outgoing connections with one or two slow clients. Does anyone know if this is true of the WCF internals?
I can also improve efficiency by coalescing incoming messages when I send them to the client. However, I suspect there's something underlying going on and coalescing won't fix the problem in the long term.
WCF Config (with company names changed):
<system.serviceModel>
<host>
<baseAddresses>
<add baseAddress="net.tcp://localhost:8100/Publisher"/>
</baseAddresses>
</host>
<endpoint address="ThePublisher"
binding="netTcpBinding"
bindingConfiguration="Tcp"
contract="Company.Product.Server.Publisher.IPublisher" />
</behavior>
Code used to send messages:
Private Sub HandleDataBackground(ByVal sender As Object, ByVal e As Timers.ElapsedEventArgs)
If Me._FeedDataQueue.Count > 0 Then
' Dequeue any items received in last 50ms.
While True
Dim dataAndReceivedTime As DataWithReceivedTimeArg
SyncLock Me._FeedDataQueue
If Me._FeedDataQueue.Count = 0 Then Exit While
dataAndReceivedTime = Me._FeedDataQueue.Dequeue()
End SyncLock
' Publish data to all clients.
Me.SendDataToClients(dataAndReceivedTime)
End While
End If
End Sub
Private Sub SendDataToClients(ByVal data As DataWithReceivedTimeArg)
Dim clientsToReceive As IEnumerable(Of ClientInformation)
SyncLock Me._ClientInformation
clientsToReceive = Me._ClientInformation.Values.Where(Function(c) Contract.CollectionContains(c.ContractSubscriptions, data.Data.Contract) AndAlso c.IsUsable).ToList()
End SyncLock
For Each clientInfo In clientsToReceive
Dim futureChangeMethod As New InvokeClientCallbackDelegate(Of DataItem)(AddressOf Me.InvokeClientCallback)
futureChangeMethod.BeginInvoke(clientInfo, data.Data, AddressOf Me.SendDataToClient)
Next
End Sub
Private Sub SendDataToClient(ByVal callback As IFusionIndicatorClientCallback, ByVal data As DataItem)
' Send
callback.ReceiveData(data)
End Sub
Private Sub InvokeClientCallback(Of DataT)(ByVal client As ClientInformation, ByVal data As DataT, ByVal method As InvokeClientCallbackMethodDelegate(Of DataT))
Try
' Send
If client.IsUsable Then
method(client.CallbackObject, data)
client.LastContact = DateTime.Now
Else
' Make sure the callback channel has been removed.
SyncLock Me._ClientInformation
Me._ClientInformation.Remove(client.SessionId)
End SyncLock
End If
Catch ex As CommunicationException
....
Catch ex As ObjectDisposedException
....
Catch ex As TimeoutException
....
Catch ex As Exception
....
End Try
End Sub
A sample of one of the message types:
<DataContract(), KnownType(GetType(DateTimeOffset)), KnownType(GetType(DataItemDepth)), KnownType(GetType(DataItemDepthDetail)), KnownType(GetType(DataItemHistory))> _
Public MustInherit Class DataItem
Implements ICloneable
Protected _Contract As String
Protected _MessageId As Guid
Protected _TradeDate As DateTime
<DataMember()> _
Public Property Contract() As String
...
End Property
<DataMember()> _
Public Property MessageId() As Guid
...
End Property
<DataMember()> _
Public Property TradeDate() As DateTime
...
End Property
Public MustOverride Function Clone() As Object Implements System.ICloneable.Clone
End Class
<DataContract()> _
Public Class DataItemDepth
Inherits DataItem
Protected _VolumnPriceDetail As IList(Of DataItemDepthItem)
<DataMember()> _
Public Property VolumnPriceDetail() As IList(Of DataItemDepthItem)
...
End Property
Public Overrides Function Clone() As Object
...
End Function
End Class
<DataContract()> _
Public Class DataItemDepthItem
Protected _Volume As Int32
Protected _Price As Int32
Protected _BidOrAsk As BidOrAsk ' BidOrAsk is an Int32 enum
Protected _Level As Int32
<DataMember()> _
Public Property Volume() As Int32
...
End Property
<DataMember()> _
Public Property Price() As Int32
...
End Property
<DataMember()> _
Public Property BidOrAsk() As BidOrAsk ' BidOrAsk is an Int32 enum
...
End Property
<DataMember()> _
Public Property Level() As Int32
...
End Property
End Class

After a long support request with Microsoft support, we managed to identify the issue.
Calling WCF channel methods using Begin/End Invoke delegate pattern actually turns into synchronous calls, not asynchronous.
The correct way to asynchronously call WCF methods is by any way except async delegates, which may include the thread pool, raw threads or WCF async callbacks.
In the end I used WCF async callbacks (which can be applied to a callback interface, although I couldn't find specific examples of that).
The following link makes this more explicit:
https://learn.microsoft.com/en-us/archive/blogs/drnick/begininvoke-bugs

Related

Visual basic programmatically pass username and password to https url to make webbrowser display webpage and also download from webpage

With normal HTTP I can download upload and navigate to routers but I can't find any code to do any of that when the routers are on HTTPS.
To download I use this:
Try
My.Computer.Network.DownloadFile("http://" & "180.29.74.70" & "/cgi-bin/log.cgi", "C:\Users\ssb\Desktop\randomword.txt", "username", "password")
WebBrowser1.Refresh()
Catch ex As Exception
MessageBox.Show("Router not sufficient for operation Return for Inspection cannot download log file")
End Try
To upload a file I use this:
My.Computer.Network.UploadFile("C:\Users\ssb\Desktop\tomtn.txt", "http://" & "180.29.74.70" & "/cgi-bin/updateconfig.cgi", "username", "password")
To navigate to a web page on HTTP I use this:
WebBrowser1.Navigate("https://username:password#180.29.74.70 ")
But when I use HTTPS:
WebBrowser1.Navigate("https://username:password#180.29.74.70 ")
I get this security alert:
Then I click on yes and it goes to the pageā€”but I need the code to bypass any security questions like these.
Even though they're loosely related, you've presented two separate questions here.
Why is the call failing when I use the WebBrowser control to load a page via HTTPS?
Why is the call failing when I use the DownloadFile() method to download a file via HTTPS?
First, you need to eliminate the possibility that your code is failing. Try both of the tasks above using public HTTPS URLs that are known to work correctly.
If you discover that the source of the problem is your private URL, you may want to consider whether you want to ignore SSL errors in your WebBrowser control.
You can do so using the (untested, translated to VB) code from this blog post:
Partial Public Class Form1
Inherits Form
Private WithEvents WebBrowser As New WebBrowser
Private Sub WebBrowser_DocumentCompleted(Sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WebBrowser.DocumentCompleted
If e.Url.ToString() = "about:blank" Then
'create a certificate mismatch
WebBrowser.Navigate("https://74.125.225.229/")
End If
End Sub
End Class
<Guid("6D5140C1-7436-11CE-8034-00AA006009FA")>
<InterfaceType(ComInterfaceType.InterfaceIsIUnknown)>
<ComImport>
Public Interface UCOMIServiceProvider
<PreserveSig>
Function QueryService(<[In]> ByRef guidService As Guid, <[In]> ByRef riid As Guid, <Out> ByRef ppvObject As IntPtr) As <MarshalAs(UnmanagedType.I4)> Integer
End Interface
<ComImport>
<ComVisible(True)>
<Guid("79eac9d5-bafa-11ce-8c82-00aa004ba90b")>
<InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IWindowForBindingUI
<PreserveSig>
Function GetWindow(<[In]> ByRef rguidReason As Guid, <[In], Out> ByRef phwnd As IntPtr) As <MarshalAs(UnmanagedType.I4)> Integer
End Interface
<ComImport>
<ComVisible(True)>
<Guid("79eac9d7-bafa-11ce-8c82-00aa004ba90b")>
<InterfaceTypeAttribute(ComInterfaceType.InterfaceIsIUnknown)>
Public Interface IHttpSecurity
'derived from IWindowForBindingUI
<PreserveSig>
Function GetWindow(<[In]> ByRef rguidReason As Guid, <[In], Out> ByRef phwnd As IntPtr) As <MarshalAs(UnmanagedType.I4)> Integer
<PreserveSig>
Function OnSecurityProblem(<[In], MarshalAs(UnmanagedType.U4)> dwProblem As UInteger) As Integer
End Interface
Public Class MyWebBrowser
Inherits WebBrowser
Public Shared IID_IHttpSecurity As New Guid("79eac9d7-bafa-11ce-8c82-00aa004ba90b")
Public Shared IID_IWindowForBindingUI As New Guid("79eac9d5-bafa-11ce-8c82-00aa004ba90b")
Public Const S_OK As Integer = 0
Public Const S_FALSE As Integer = 1
Public Const E_NOINTERFACE As Integer = &H80004002
Public Const RPC_E_RETRY As Integer = &H80010109
Protected Overrides Function CreateWebBrowserSiteBase() As WebBrowserSiteBase
Return New MyWebBrowserSite(Me)
End Function
Private Class MyWebBrowserSite
Inherits WebBrowserSite
Implements UCOMIServiceProvider
Implements IHttpSecurity
Implements IWindowForBindingUI
Private myWebBrowser As MyWebBrowser
Public Sub New(myWebBrowser As MyWebBrowser)
MyBase.New(myWebBrowser)
Me.myWebBrowser = myWebBrowser
End Sub
Public Function QueryService(ByRef guidService As Guid, ByRef riid As Guid, ByRef ppvObject As IntPtr) As Integer Implements UCOMIServiceProvider.QueryService
If riid = IID_IHttpSecurity Then
ppvObject = Marshal.GetComInterfaceForObject(Me, GetType(IHttpSecurity))
Return S_OK
End If
If riid = IID_IWindowForBindingUI Then
ppvObject = Marshal.GetComInterfaceForObject(Me, GetType(IWindowForBindingUI))
Return S_OK
End If
ppvObject = IntPtr.Zero
Return E_NOINTERFACE
End Function
Public Function GetWindow(ByRef rguidReason As Guid, ByRef phwnd As IntPtr) As Integer Implements IHttpSecurity.GetWindow, IWindowForBindingUI.GetWindow
If rguidReason = IID_IHttpSecurity OrElse rguidReason = IID_IWindowForBindingUI Then
phwnd = myWebBrowser.Handle
Return S_OK
Else
phwnd = IntPtr.Zero
Return S_FALSE
End If
End Function
Public Function OnSecurityProblem(dwProblem As UInteger) As Integer Implements IHttpSecurity.OnSecurityProblem
'ignore errors
'undocumented return code, does not work on IE6
Return S_OK
End Function
End Class
End Class
Regarding problem #2: It appears you may be confusing WebBrowser and DownloadFile(). As you've probably already discovered, the WebBrowser control doesn't download files. However, you can simulate the behavior using this technique:
Partial Public Class Form2
Inherits Form
Private Sub WebBrowser_Navigating(Sender As Object, e As WebBrowserNavigatingEventArgs) Handles WebBrowser.Navigating
Dim sFilePath As String
Dim oClient As Net.WebClient
' This can be any conditional criteria you wish '
If (e.Url.Segments(e.Url.Segments.Length - 1).EndsWith(".pdf")) Then
SaveFileDialog.FileName = e.Url.Segments(e.Url.Segments.Length - 1)
e.Cancel = True
If SaveFileDialog.ShowDialog() = DialogResult.OK Then
sFilePath = SaveFileDialog.FileName
oClient = New Net.WebClient
AddHandler oClient.DownloadFileCompleted, New AsyncCompletedEventHandler(AddressOf DownloadFileCompleted)
oClient.DownloadFileAsync(e.Url, sFilePath)
End If
End If
End Sub
Private Sub DownloadFileCompleted(Sender As Object, e As AsyncCompletedEventArgs)
MessageBox.Show("File downloaded")
End Sub
Private WithEvents SaveFileDialog As New SaveFileDialog
Private WithEvents WebBrowser As New WebBrowser
End Class
In any event, the first step in solving this is to figure out whether it's your code or the private URL that's causing your issue.
The main thing needed here is to programatically download a file from a https url while using a username and password blocked by the security certificate issue
and the solution after searching for 2 weeks is
To Download a file you can disable the security cerificate request temporaraly with the following code then after the code ran it enables the security certicate again
First code you dont even need a browser it automatically saves the file to you desktop
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
'check if a simular file doesnt exists so you can create a new file and deletes the file if it exists
If File.Exists("C:\pathtoyourfile\yourfilename.txt") Then
File.Delete("C:\pathtoyourfile\yourfilename.txt")
End If
'Type this before your download or hhtps request
'ByPass SSL Certificate Validation Checking
System.Net.ServicePointManager.ServerCertificateValidationCallback =
Function(se As Object,
cert As System.Security.Cryptography.X509Certificates.X509Certificate,
chain As System.Security.Cryptography.X509Certificates.X509Chain,
sslerror As System.Net.Security.SslPolicyErrors) True
'Call web application/web service with HTTPS URL here
'=========================================================================================
'ServicePointManager.ServerCertificateValidationCallback = AddressOf AcceptAllCertifications
Try
My.Computer.Network.DownloadFile("https://176.53.78.22/filenameonserveryouwanttodownload", "C:\pathtoyourfile\yourfilename.txt", "Yourusername", "yourpassword")
WebBrowser1.Refresh()
Catch ex As Exception
MessageBox.Show("message saying something didnt work")
'exit sub if it worked
Exit Sub
End Try
MessageBox.Show(" message saying it worked")
'=========================================================================================
'Restore SSL Certificate Validation Checking
System.Net.ServicePointManager.ServerCertificateValidationCallback = Nothing
End Sub
then to browse to a webaddress the following code will popup and the security popup will popup but just select yes browsing on the webpage works normally
WebBrowser1.Navigate("https://username:password#180.29.74.70 ")
As you said:
[...] I need the code to bypass any security questions like these.
In other word, you need to "automatically accept self signed SSL certificate", so in my opinion it is a duplicate question with : VB .net Accept Self-Signed SSL certificate, which may fit your needs.
and most especially slaks answer:
In VB.Net, you need to write:
ServicePointManager.ServerCertificateValidationCallback = AddressOf AcceptAllCertifications

Write to a log file asynchronously from asp.net task

Using VB.Net (Framework version 4.5.1)
I have a program that sets up a list of (System.Threading.Tasks.Task) tasks that are executed as follows (only relevant code is shown):
po = New System.Threading.Tasks.ParallelOptions()
po.MaxDegreeOfParallelism = 5
Parallel.ForEach( task_list, po, AddressOf do_work )
The program works fine, but I want to add a log file rather than using just Console.WriteLine()
In the do_work() Sub, I want to write to a log file, for example:
Sub do_work( param As String )
Dim thread_id as String = "Thread ID " & System.Threading.Thread.CurrentThread.ManagedThreadId.ToString() & ": "
joblog_append( thread_id & "Beg" )
joblog_append( thread_id & "param = " & param )
joblog_append( thread_id & "End" )
End Sub
Ideally, I would like to create three functions such as:
joblog_open() to open a log file at the start o the program.
joblog_append() to append to the log file; callable from within any task
joblog_close() to close the log file
What is the correct way to implement such logging when the joblog_append() will be called from multiple tasks being executed on separate threads?
All attempts I have tried so far seem to be hit and miss; sometimes the data is written to the output file, sometimes it is not.
Any advice (or better yet, a code example) would be most appreciated.
Thanks in advance.
I think that the issue you have is due to the fact multiple threads are accessing the file at the same time.
A better approach would be to make sure only one thread access the file at a time. You could use a lock (see this SO) or append the messages to be written to a concurrentQueue of string, then process that queue on another thread.
The joblog_append calls _Logger.Log, which in turns enqueue the message and start a new thread to process the queue.
private _Logger as new Logger
Private Sub joblog_append(Message As String)
_Logger.Log(Message)
End Sub
The logger class performs the following.
Append message to to a concurrent queue
Create and start a task (if no one already running) to write queue content to the file.
Set the task to nothing when completed
In the event the task is already created, the message is enqueued and the While condition in the task itself should take care of any messages added while it's running.
'Missing: Idisposable, Fileaccess.Shared,
'TODO: remove debugger.break
Public class Logger
Public Property Messages As new ConcurrentQueue(Of string)
private _WorkerTask as Task
private event WorkerTaskCompleted
private _Stream as FileStream
private _Writer as StreamWriter
Public sub New()
_Stream = io.file.OpenWrite("Mylog.txt")
_Writer = New StreamWriter(_Stream)
End sub
Public sub Log(Message as string)
Messages.Enqueue(Message)
if _WorkerTask Is Nothing
_WorkerTask = New Task(sub()
While Messages.Any
Dim CurrentMessage as string = ""
if Messages.TryDequeue(CurrentMessage)
_Writer.WriteLine(CurrentMessage)
else
debugger.Break
End If
End While
_Writer.Flush
_Stream.Flush
RaiseEvent WorkerTaskCompleted
End Sub)
_WorkerTask.Start
End If
End sub
Private Sub Logger_WorkerTaskCompleted() Handles Me.WorkerTaskCompleted
_WorkerTask = Nothing
End Sub
End Class
Please note. This is my approach to this problem but I do not have anything similar implemented and tested. Therefore, you will have to make your tests to confirm it is working properly.

Cookies not preserved in ASHX handler

This is a single page application which uses a cookie to hold a guid of the users server login. Server httpgets are all wrapped into a single ASHX file and are all invoked by jQuery $.ajax(). The only web file in the project is a htm file which hosts all the javascript to make it happen.
When I set a cookie using Response.Cookies I get a different value when I inspect it to Request.Cookies, and when I set a Response.Cookie, the corresponding Request.Cookie on the next server visit still contains some old value, not the value just set.
I have added these two lines to the handler ASHX file:
Implements System.Web.SessionState.IRequiresSessionState
Implements System.Web.SessionState.IReadOnlySessionState
I have also changed the IsReusable property to true:
ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return True
End Get
End Property
Neither of the above changes helped.
When I inspect Session.SessionID, its different on every server call, which I suspect is the root of the problem - should it not be the same ?
The webserver is the Visual Studio 2015 debugging web server, not IIS.
Cookies are got using request.Cookies("name").Value and the set function is as follows:
Public Sub SetCookie(ByVal Response As System.Web.HttpResponse, ByVal sCookie As String, ByVal sValue As String, Optional ByVal lTimePeriodMinutes As Integer = 30 * 24 * 60)
sCookie = sCookie.ToLower
Response.Cookies.Remove(sCookie)
Dim obj As New System.Web.HttpCookie(sCookie, sValue)
obj.Expires = DateAdd(Microsoft.VisualBasic.DateInterval.Minute, lTimePeriodMinutes, Now)
Response.Cookies.Add(obj)
End Sub
The above has worked for many years on an old version of the app.
The problem is the same on IE and Firefox

How to store and access per call data in WCF

I'm trying to set up some WCF services that are connected and pass a custom HTTP header from service to service.
That is my client call ServiceX, which calls ServiceY, which writes to the DB.
They are originally called from a Silverlight 5 client in some cases, other cases from an ASP.NET web app.
I implemented IClientMessageInspector and IDispatchMessageInspector to pass the header from service to service, and in the DispatchMessageInspector I wrote the header to an implementation of IExtension(Of OperationContext) (see below).
However, I wanted this data only to exist for the duration of the call, but it seems to be sticking around in the services under certain circumstances as I keep seeing the same header data repeated in different calls.
Ultimately, I want to be able to pass a custom header to a WCF service, persist it only while that call exists, send it to the next service in the header, and wipe out that service instance. Am I wrong in my thinking that using a PerCall WCF service setup and OperationContext is the right way to do that?
Here is my implementation of IExtension(Of OperationContext). The auditTransactionId is the thing I want to pass in the service. As well, the Current() property is where I keep seeing existing data:
Imports System.ServiceModel
Public Class CustomOperationContextExtension
Implements IExtension(Of OperationContext)
Private ReadOnly m_items As IDictionary(Of String, Object)
Private m_auditTransactionId As String
Private Sub New()
m_items = New Dictionary(Of String, Object)()
End Sub
Public ReadOnly Property Items() As IDictionary(Of String, Object)
Get
Return m_items
End Get
End Property
Public Property AuditTransactionId() As String
Get
Return m_auditTransactionId
End Get
Set(value As String)
m_auditTransactionId = value
End Set
End Property
Public Shared ReadOnly Property Current() As CustomOperationContextExtension
Get
If (OperationContext.Current IsNot Nothing) Then
Dim context As CustomOperationContextExtension = OperationContext.Current.Extensions.Find(Of CustomOperationContextExtension)()
If context Is Nothing Then
context = New CustomOperationContextExtension()
OperationContext.Current.Extensions.Add(context)
End If
Return context
End If
Return Nothing
End Get
End Property
Public Sub Attach(owner As OperationContext) Implements IExtension(Of System.ServiceModel.OperationContext).Attach
End Sub
Public Sub Detach(owner As OperationContext) Implements IExtension(Of System.ServiceModel.OperationContext).Detach
End Sub
End Class
EDIT:
When I say that data is sticking around, I mean that when I call Current in a new service call I expect the Extensions list to be empty (in the code below in the Current() property), but there is always an existing instance of CustomOperationContextExtension there already that is left over fro a previous call. I'm not sure under which circumstances this happens.

Instantiating a class within WCF

I'm writing a WCF WebMethod to upload files to, of which I taken snippets from around the web. The WCF interface looks like this:
<ServiceContract()>
Public Interface ITransferService
<OperationContract()>
Sub UploadFile(ByVal request As RemoteFileInfo)
End Interface
<MessageContract()>
Public Class RemoteFileInfo
Implements IDisposable
<MessageHeader(MustUnderstand:=True)>
Public FileName As String
<MessageHeader(MustUnderstand:=True)>
Public Length As Long
<MessageBodyMember(Order:=1)>
Public FileByteStream As System.IO.Stream
Public Sub Dispose() Implements IDisposable.Dispose
If FileByteStream IsNot Nothing Then
FileByteStream.Close()
FileByteStream = Nothing
End If
End Sub
End Class
Within ASP.NET, when the web method is consumed, for some reason it only works when the interface is used as part of the instantiation of RemoteFileInfo:
Protected Sub btn_Click(sender As Object, e As EventArgs) Handles btn.Click
If fu.HasFile Then
Dim fi As New System.IO.FileInfo(fu.PostedFile.FileName)
' this is the line in question --------------
Dim cu As ServiceReference1.ITransferService = New ServiceReference1.TransferServiceClient()
' -------------------------------------------
Dim uri As New ServiceReference1.RemoteFileInfo()
Using stream As New System.IO.FileStream(fu.PostedFile.FileName, IO.FileMode.Open, IO.FileAccess.Read)
uri.FileName = fu.FileName
uri.Length = fi.Length
uri.FileByteStream = stream
cu.UploadFile(uri)
End Using
End If
End Sub
Can anyone advise why it is not possible to create an instance of TransferService using the following approach:
Dim cu As New ServiceReference1.TransferServiceClient()
If I try the above, it breaks this line:
cu.UploadFile(uri)
...and UploadFile must be called with three parameters (FileName, Length, FileByteStream) even there is no method that uses this signature.
Why is the Interface required when creating an instance of this class please?
When you create the proxy for your service with the "Add Service Reference" dialog, by default the proxy creation code will "unwrap" message contracts, like the one you have. If you want the message contract to appear as you defined on the server side on your proxy, you need to select the "Advanced" tab, and check the "Always generate message contracts" option. With that you'll get the message contract in your client as well.
The issue is that when a MessageContract is encountered as a parameter, the WCF client generation assumes by default that you want to implement a messaging-style interface, and provides the discrete properties from the message contract as part of the client-side interface.
The Using Messaging Contracts article in MSDN contains a very detailed description of what can be done with a messaging contract and I suspect that Microsoft chose this default behavior because of some of the "games" that you can play with the messages.
However, if you examine the code generated for your UploadFile on the client side, there are some interesting tidbits that help to explain what is going on.
The first is the comments for the UploadFile method in the interface:
'CODEGEN: Generating message contract since the operation UploadFile is neither RPC nor document wrapped.
...
Function UploadFile(ByVal request As ServiceReference1.RemoteFileInfo) As ServiceReference1.UploadFileResponse
This implies that the contract would have been generated differently if the message contract had a different implementation.
The second is that you will see that there is nothing special about the code that is used to actually make the service call:
Public Sub UploadFile(ByVal FileName As String, ByVal Length As Long, ByVal FileByteStream As System.IO.Stream)
Dim inValue As ServiceReference1.RemoteFileInfo = New ServiceReference1.RemoteFileInfo()
inValue.FileName = FileName
inValue.Length = Length
inValue.FileByteStream = FileByteStream
Dim retVal As ServiceReference1.UploadFileResponse = CType(Me,ServiceReference1.ITransferService).UploadFile(inValue)
End Sub
So in this case, your code is doing exactly what the generated code does. However, if the MessageContract were more complex, I suspect that this would no longer be the case.
So, for your question:
Can anyone advise why it is not possible to create an instance of
TransferService using the following approach...
There is no reason not to take this approach as long as you verify that the implementation of the method call is functionality equivalent to your code.
There are a couple of options for changing the default generation of the method in the client:
1) Remove the MessageContract attribute from the RemoteFileInfo class.
2) Although it seems to be counter-intuitive, you can check the Always generate message contracts checkbox in the Configure Service Reference Dialog Box.

Resources