File Transfer via WCF - wcf

I'm a bit of the newbie to WCF so i would really appreciate if you could answer as detailed as possible :) i have a WCF service library and a WPF application (who is a client). the wanted result is an application that will enable file sharing between the connected clients.I build a really basic WCF service library with one method:
[ServiceContract]
public interface IFileService
{
[OperationContract]
byte[] GetFile(string fullPath);
}
And implemented this method like this:
public class FileService : IFileService
{
public byte[] GetFile(string fullPath)
{
return System.IO.File.ReadAllBytes(fullPath);
}
}
This is the App.config file in the WPF client project:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_IFileService" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:9355/TankusFileTransferService/Service/"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IFileService"
contract="TankusFileService.IFileService" name="WSHttpBinding_IFileService">
<identity>
<userPrincipalName value="GIL-LAPTOP\Gil" />
</identity>
</endpoint>
</client>
</system.serviceModel>
And this is the code from the main window WPF application:
public partial class MainWindow : Window
{
ServiceHost sh;
TankusFileService.FileServiceClient fsc;
public MainWindow()
{
InitializeComponent();
}
private void btn_Connect_Click(object sender, RoutedEventArgs e)
{
Uri uri = new Uri("http://127.0.0.1:1234/");
sh = new ServiceHost(typeof(TankusFileTransferService.FileService), uri);
sh.Open();
lbl_Listener.Content = sh.Description.Endpoints[0].Address.ToString();
}
private void btn_Disconnect_Click(object sender, RoutedEventArgs e)
{
sh.Close();
lbl_Listener.Content = string.Empty;
}
private void btn_GetFile_Click(object sender, RoutedEventArgs e)
{
fsc = new TankusFileService.FileServiceClient();
fsc.Endpoint.Address = new EndpointAddress("http://127.0.0.1:1234/");
fsc.Endpoint.Binding = new BasicHttpBinding();
byte[] bytes = fsc.GetFile(#"D:\mika.txt");
System.IO.File.WriteAllBytes(#"D:\mika_new.txt", bytes);
}
}
After i press the connect button and initialize the ServiceHost object so it can start listening i press the getFile button. when the GetFile() function is called it throws a TimeoutException. Why is this? am i even on the right way for accomplishing my wanted application? Thanks :)

You are likely getting a TimeoutException because it is taking longer to send the file than is allowed by your service.
In your config file for both the server and the client be sure to increase the receiveTimeout and sendTimeout.
You may also bump into size limits as WCF configure the maximum message size, and the file would be considered part of the message. Look at maxBufferPoolSize, maxReceivedMessageSize, and the members below
<readerQuotas
maxDepth="32" maxStringContentLength="8192"
maxArrayLength="16384" maxBytesPerRead="4096"
maxNameTableCharCount="16384" />

A synchronous web service request is not the best way to transfer files about. Even if it works, if you need to scale the endpoint to process concurrent requests you will quickly run in to trouble. By uploading files to service endpoints you are compromising the availability of the endpoint.
A better solution - the WPF app writes the filestream to disk (or a database, ftp server, or queue), then sends a quick one way command message to the server, which then goes and grabs the file.
This is hugely more scalable and will result in far fewer availability-type exceptions.
UPDATE
In my experience, when you upload large files to web service endpoints you can get availability problems, especially if there is any significant concurrency. You can plan for this stuff if you know what your upper bounds are (file sizes, concurrent connections, etc.) and you can formalise this as a service level agreement, but the nature of what you are trying to do (peer-to-peer) is by definition a volatile environment where such planning would be difficult.
However, that said, the fact that your requirement is for P2P means that there ideally shouldn't be a centralised environment to implement the type of store-and-retrieve messaging pattern I am suggesting.
Windows Azure blob storage is an example of how this may be achieved.

Related

Programmatically adding an endpoint

I have a WCF service that I am connecting in client application. I am using following in configuration file.
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="MyNameSpace.TestService" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="2147483647" maxBufferPoolSize="524288" maxReceivedMessageSize="2147483647"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="2147483647" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:9100/TestService" binding="basicHttpBinding"
bindingConfiguration="MyNameSpace.TestService" contract="TestService.IService" name="MyNameSpace.TestService" />
</client>
</system.serviceModel>
In the code, I am calling API on this service as follows,
TestServiceClient client = new TestServiceClient()
client.BlahBlah()
Now I want to defined endpoint porgramatically. How can that be done? I commented out section from config file as I was thinking I will have to put some code on TestServiceClient instance to add endpoint dynamically but then it throws following exception at the point where TestServiceClient is instantiated.
Could not find default endpoint element that references contract
'TestService.IService' in the ServiceModel client configuration
section. This might be because no configuration file was found for
your application, or because no endpoint element matching this
contract could be found in the client element.
How can I accomplish this? Also any point on code examples for adding endpoint programmatically will be appreciated.
To create endpoints and bindings programmatically, you could do this on the service:
ServiceHost _host = new ServiceHost(typeof(TestService), null);
var _basicHttpBinding = new System.ServiceModel.basicHttpBinding();
//Modify your bindings settings if you wish, for example timeout values
_basicHttpBinding.OpenTimeout = new TimeSpan(4, 0, 0);
_basicHttpBinding.CloseTimeout = new TimeSpan(4, 0, 0);
_host.AddServiceEndpoint(_basicHttpBinding, "http://192.168.1.51/TestService.svc");
_host.Open();
You could also define multiple endpoints in your service config, and choose which one to connect to dynamically at run time.
On the client program you would then do this:
basicHttpBinding _binding = new basicHttpBinding();
EndpointAddress _endpoint = new EndpointAddress(new Uri("http://192.168.1.51/TestService.svc"));
TestServiceClient _client = new TestServiceClient(_binding, _endpoint);
_client.BlahBlah();
Can you just use:
TestServiceClient client = new TestServiceClient();
client.Endpoint.Address = new EndPointAddress("http://someurl");
client.BlahBlah();
Note that your binding configuration will no longer apply, as you're not using that endpoint configuration in your configuration file. You'll have to override that, too.
You can try:
TestServiceClient client = new TestServiceClient("MyNameSpace.TestService")
client.BlahBlah()
if not recheck namespace in file TestService is correct?

LightSwitch application chokes while instantiating a WCF proxy

I'm trying to create a LightSwitch management panel for a web-based app. Thats why I was setting up a WCF RIA service to interface with the WCF service of the web app. While testing the loading of the users, I discovered that LightSwitch said that it couldn't load the resource. The Immediate Window told me that a System.InvalidOperationException had occured within System.ServiceModel.dll but VS didnt actually point me towards the loc where the error would have originated. After some line for line code execution, I discovered it choked at the instantiation of the WCF proxy.
An example of the code on the WCF RIA service Class:
Public Class RIAInterface
Inherits DomainService
Private WCFProxy As New Service.UserClient() '<-- Choke Point
Public Sub New()
WCFProxy.Open()
End Sub
<Query(IsDefault:=True)>
Public Function GetUsers() As IQueryable(Of User)
Dim TempList As New List(Of User)
For Each User As Service.User In WCFProxy.GetUsers()
TempList.Add(New User With {.ID = User.ID, .FullName = User.FullName, .EmailAddress = User.Email, .Username = User.UserName, .Class = User.Class.Name, .AccountType = User.Privilege.Name})
Next
Return TempList.AsQueryable
End Function
End Class
After some fooling arround with the RIA service and LightSwitch, something changed. I ran the app and got an actual exception.
Exception Details:
Could not find endpoint element with name 'EduNetBackEnd_IUser' and contract 'EduNet.IUser' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this name could be found in the client element.
This is the the ServiceModel configuration in the App.config:
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="EduNetBackEnd_IUser" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" bypassProxyOnLocal="false"
transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:15:00"
enabled="true" />
<security mode="None">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="[Service_Address]"
binding="wsHttpBinding" bindingConfiguration="EduNetBackEnd_IUser"
contract="EduNet.IUser" name="EduNetBackEnd_IUser" />
</client>
</system.serviceModel>

net.tcp service works when hosted by Net.Tcp Listener Adapter, but not Windows Service

I created a WCF net.tcp service and hosted it using the Net.Tcp Listener Adapter, and it works great - I have some messaging set up on the callback so the service updates the client with the status. Now, I'm trying to get it to work by being hosted via a Windows Service, and all I'm doing is creating a ServiceHost using the same class that the original uses:
using System.Diagnostics;
using System.ServiceModel;
using System.ServiceProcess;
using BuilderService;
namespace BuilderWindowsService
{
public class BuilderWindowsService : ServiceBase
{
public ServiceHost ServiceHost = null;
public BuilderWindowsService()
{
ServiceName = ServiceNames.Builder;
}
public static void Main()
{
Run(new BuilderWindowsService());
}
protected override void OnStart(string[] args)
{
if (ServiceHost != null)
ServiceHost.Close();
ServiceHost = new ServiceHost(typeof(Builder));
ServiceHost.Open();
}
protected override void OnStop()
{
if(ServiceHost != null)
{
ServiceHost.Close();
ServiceHost = null;
}
}
}
}
I can connect to the service and send a request, but it never responds nor times out. I know I'm hitting the Windows Service because I have it on another port (8002), and I can add it as a reference using that.
My App.config for the Windows Service is pretty much identical to the Web.config of the original too. Same thing for the client I am using, except it is pointing to the 8002 endpoint instead of 808. Also, I already have this working for another service, doing the exact same setup, but for some reason this one never responds.
UPDATE
I created a little client app to test out directly hitting the windows service to rule out anything interfering, and it generated the following app.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="NetTcpBinding_IBuilder"
closeTimeout="00:01:00"
openTimeout="00:01:00"
receiveTimeout="00:10:00"
sendTimeout="00:01:00"
transactionFlow="false"
transferMode="Buffered"
transactionProtocol="OleTransactions"
hostNameComparisonMode="StrongWildcard"
listenBacklog="10"
maxBufferPoolSize="2147483647"
maxBufferSize="2147483647"
maxConnections="10"
maxReceivedMessageSize="2147483647">
<readerQuotas maxDepth="32"
maxStringContentLength="2147483647"
maxArrayLength="2147483647"
maxBytesPerRead="4096"
maxNameTableCharCount="2147483647" />
<reliableSession ordered="true"
inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows"
protectionLevel="EncryptAndSign" />
<message clientCredentialType="Windows"
algorithmSuite="Default" />
</security>
</binding>
</netTcpBinding>
</bindings>
<client>
<endpoint address="net.tcp://localhost:8002/BuilderService/Builder.svc"
binding="netTcpBinding"
bindingConfiguration="NetTcpBinding_IBuilder"
contract="RGBRef.IBuilder"
name="NetTcpBinding_IBuilder">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
Which looks pretty normal to me (note: I manually upped the buffer/string length values to the maximum). Only things that are different from my original config:
transferMode="Buffered"
transactionProtocol="OleTransactions"
listenBacklog="10"
<transport clientCredentialType="Windows"
protectionLevel="EncryptAndSign" />
Not sure if the service is expecting those or something. Either way, it's still not getting any response back, nor an error.
Perhaps the service is faulting since it now runs under different credentials as a Windows Service. Write some EventLog entries to trace where the fault is occurring. I don't believe is the callback, I suspect it's something else in the service failing.

Disable gzip compression in request to GeoCoder web site

I have a example application written in Delphi using /N Software's SOAP component. This application communicates with the GeoCoder site (see geocoder.us) to get latitude and longitude from a provided address. Works ok.
Wrote a very simple WCF client to basically do the same thing but it does not work. I used Fiddler2 to see what is happening.
The Delphi application gets back basic text. My WCF application gets back content that is gzip compressed. This must be the default as I did not set this. When the response comes back there is no means to decompress the data.
I see many many web pages going into detail about setting gzip compression but nothing about disabling this.
The question is how can I get my WCF client to request from the GeoCoder site, a response in plain text (I know that compression is good etc) but in this instance I just want to see this working.
The sites WSDL is at http://rpc.geocoder.us/dist/eg/clients/GeoCoder.wsdl
My source code (its very elementary as I learning this stuff)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.ServiceModel;
using GeoCodeTest.ServiceReference1;
namespace GeoCodeTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private GeocoderResult[] LocationResult = null;
private GeoCode_PortTypeClient proxy = null;
private void btnGo_Click(object sender, EventArgs e)
{
LocationResult = proxy.geocode(txtLocation.Text); // always returns a null !!!
geocoderResultBindingSource.DataSource = LocationResult;
}
private void Form1_Load(object sender, EventArgs e)
{
proxy = new GeoCode_PortTypeClient("GeoCode_Port");
}
}
}
This is the app.config file that was generated.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="GeoCode_Binding" closeTimeout="00:01:00" openTimeout="00:01:00"
receiveTimeout="00:10:00" sendTimeout="00:01:00" allowCookies="false"
bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard"
maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered"
useDefaultWebProxy="true">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<security mode="None">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint address="http://rpc.geocoder.us/service/soap/" binding="basicHttpBinding"
bindingConfiguration="GeoCode_Binding" contract="ServiceReference1.GeoCode_PortType"
name="GeoCode_Port" />
</client>
</system.serviceModel>
</configuration>
Is there something I can add/remove/change to the app.config so that a WCF client request will ask for plain text like the Delphi application?
Server should compress the response only if your request contains HTTP header Accept-Encoding: gzip. Check your request with Fiddler. If this header is not present in your request, the compression is controlled by some custom mechanism.
Only WCF 4 HTTP based client sends that HTTP header because it is out of the box able to decompress the message. If it doesn't there is something wrong with the message. You can turn off this feature by defining custom binding:
<bindings>
<customBinding>
<binding name="BasicWithNoCompression">
<textMessageEncoding messageVersion="Soap11" />
<httpTransport decompressionEnabled="false" />
</binding>
</customBidning>
</bindings>
I had a quick look at this, and it is NOT the compression that is causing the issue. As already mentioned, WCF clients (v4) can decompress the data fine. In fact, if you modify the outgoing request message header and remove the "gzip, deflate" from the Accept-Encoding header the response comes back already uncompressed, however the data is still not present in the return object. There seems to be an issue with the way the client has generated its datacontract I think.
If I have some more time, I will have a closer look but it may take sometime to find out where the exact mismatch(s) occur.

Issue in calling WCF Service that calls another WCF Service

We have a requirement to call a WCF service from another WCF Service. To test this I build a sample console application to display a simple string. The setup is:
Console App -> WCF Service 1 -> WCF Service 2
Console App calls a method of service 1 and the service 1 method eventually calls service 2 method to return a string. I am able to call Console -> Service 1 but Service 1 -> Service 2 is not working. It throws an exception:
"Could not find default endpoint element that references contract 'ITestService2' in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element."
To accomplish this, I have created a WCF Service 2 with one method that returns a string (nothing fancy).
namespace TestServices
{
[ServiceContract]
public interface ITestService2
{
[OperationContract]
string GetSomething(string s);
}
}
Then I create service1 - ITestService1.cs and TestService1.cs that consumes service2 method GetSomething().
namespace TestServices
{
[ServiceContract]
public interface ITestService1
{
[OperationContract]
string GetMessage(string s);
}
}
namespace TestServices
{
class TestService1 : ITestService1
{
public string GetMessage(string s)
{
TestService2 client = new TestService2();
return client.GetSomething("WELCOME " + s);
}
}
}
Note: I create a proxy for Service2 using svcutil.exe. It creates a app.config and TestService2.cs files that I copied in TestService1 project folder to reference.
Finally, I created the console app that just creates an instance of Service1 and calls the GetMessage() method.
static void Main(string[] args)
{
TestService1 client = new TestService1();
Console.WriteLine(client.GetMessage("Roger Harper"));
Console.ReadKey();
}
When I call the service 2 directly from Console application, it works without any issue. The same config and proxy class when copied with in service 1. It throws error. The config file looks like:
config file for service 1 in console application:
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ITestService1" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:3227/WCFTestSite/TestService1.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ITestService1"
contract="ITestService1" name="WSHttpBinding_ITestService1">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
config file for service 2 in service1 folder:
<configuration>
<system.serviceModel>
<bindings>
<wsHttpBinding>
<binding name="WSHttpBinding_ITestService2" closeTimeout="00:01:00"
openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00"
bypassProxyOnLocal="false" transactionFlow="false" hostNameComparisonMode="StrongWildcard"
maxBufferPoolSize="524288" maxReceivedMessageSize="65536"
messageEncoding="Text" textEncoding="utf-8" useDefaultWebProxy="true"
allowCookies="false">
<readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384"
maxBytesPerRead="4096" maxNameTableCharCount="16384" />
<reliableSession ordered="true" inactivityTimeout="00:10:00"
enabled="false" />
<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None"
realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="true"
algorithmSuite="Default" establishSecurityContext="true" />
</security>
</binding>
</wsHttpBinding>
</bindings>
<client>
<endpoint address="http://localhost:3227/WCFTestSite/TestService2.svc"
binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_ISriWCFTestService2"
contract="ITestService2" name="WSHttpBinding_ITestService2">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
</client>
</system.serviceModel>
</configuration>
Appreciate if someone can help me in resolving this issue. I also tried prefixing the contract name with namespace but it didn't work. Not sure how the same config/proxy works directly from console and not with in another service. Please HELP!!! Thanks in advance.
From my understating you have a console app that is self hosting a wcf service that service is calling a second wcf service. I am guessing you have a wcf service1 defined in dll that the console app loads and then attempts to call. I think your issue may be that sice service 1 is in a dll its not loading the config file where you have defined the link to service 2. Try creating the endpoint programmaticly and see if that gets you thorough the issue.