I've a system where I'm exchanging messages across different point to point comms channels- between Windows and embedded systems, and have done it all as pretty standard custom serialize/deserialize functions pretty much entirely done by hand, since that makes it easy to port between C# on the Windows side and C on the embedded.
Now I want to add a chunk that communicates between PCs on the net at large. Rather than do another batch of the same stuff, use TcpClient/TcpListener and keep track of overlapping messages and responses, I decided to have a look at WCF.
After looking at lots of messages on here, and docs etc elsewhere, I've come up with a very simple app that exchanges messages, with the server containing one function that takes and returns an interface instance, rather than a fixed class. Even though the example has only one kind of message- hence only one type is set using the KnownType and ServiceKnownType attributes, I picture there being a few tens of different types of messages that could be sent, and I want to be able to add them fairly easily as things evolve.
Although no errors are generated by the code, the object that's instantiated at the far end has none of the data that was sent. I've tried packet sniffing to see if I can confirm the data's actually going on the wire but I can't understand the wire protocol. So I don't know if the data's disappearing in the client on transmission or in the server. If I change the code to use instances of TestMessageType directly rather than using the interface, it works fine.
The solution's made of three projects; a "types" assembly and then client and server console apps that reference that assembly. The types assembly contains this code;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;
namespace WCF_TCP_Sandpit
{
public interface ITestInterface
{
Int64 I64Value {get; set;}
}
[ServiceContract]
public interface IServer
{
[OperationContract]
[ServiceKnownType(typeof(TestMessageType))]
ITestInterface Test(ITestInterface msg);
}
[DataContract]
[KnownType(typeof(TestMessageType))]
public class TestMessageType : ITestInterface
{
Int64 _v1;
public long I64Value
{
get { return _v1; }
set { _v1 = value; }
}
public static Type[] KnownTypes()
{
return new Type[] { typeof(TestMessageType) };
}
}
}
The server code is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using WCF_TCP_Sandpit;
using System.Runtime.Serialization;
namespace Server
{
class Program : IServer
{
static void Main(string[] args)
{
using (ServiceHost serviceHost = new ServiceHost(typeof(Program), new Uri("net.tcp://127.0.0.1:9000")))
{
serviceHost.Open();
// The service can now be accessed.
Console.WriteLine("The service is ready.");
Console.WriteLine("Press <ENTER> to terminate service.");
Console.WriteLine();
Console.ReadLine();
}
}
#region IServer Members
public ITestInterface Test(ITestInterface msg)
{
ITestInterface reply = new TestMessageType();
reply.I64Value = msg.I64Value * 2;
return reply;
}
#endregion
}
}
and the client code is
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WCF_TCP_Sandpit;
using System.ServiceModel;
namespace Client
{
class Program
{
static void Main(string[] args)
{
ITestInterface m,r;
int i = 0;
ChannelFactory<WCF_TCP_Sandpit.IServer> srv
= new ChannelFactory<WCF_TCP_Sandpit.IServer>
(new NetTcpBinding(), "net.tcp://127.0.0.1:9000");
WCF_TCP_Sandpit.IServer s;
s = srv.CreateChannel();
while (true)
{
m = new WCF_TCP_Sandpit.TestMessageType();
m.I64Value = i++;
r = s.Test(m);
Console.WriteLine("Sent " + m.I64Value + "; received " + r.I64Value);
System.Threading.Thread.Sleep(1000);
}
}
}
}
Can anyone cast some light on what's going wrong?
Don't you need the DataMember attribute on your I64Value property?
Related
I'm experimenting with making async WCF calls. I'm looking at making multiple calls to the same service.
From the client I create a new channel for each call, make the call (giving it a callback method), and then in the callback I close the channel.
In the service I have added a call to thread.sleep to simulate the service doing some work.
The first 20 or so calls complete ok (this number varies each time). So after what seems a random number of calls, I receive this exception on the client:
Could not connect to net.tcp://localhost:61501/Calulator. The connection attempt lasted for a time span of 00:00:02.9332933. TCP error code 10061: No connection could be made because the target machine actively refused it 127.0.0.1:61501.
So I have a couple of questions:
Am I right to be opening a new
channel for each call?
What is causing this exception?
Many thanks in advance for any help.
My code is as follows, and can also be found here:
https://subversion.assembla.com/svn/agilenet/tags/WcfStackOverflow/Wcf
Service:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Service
{
class Program
{
static void Main(string[] args)
{
NetTcpBinding netBinding = new NetTcpBinding();
ServiceHost host = null;
host = new ServiceHost(
typeof(Calculator),
new Uri("net.tcp://localhost:61501/Calulator"));
host.AddServiceEndpoint(
typeof(ICalculator),
netBinding,
string.Empty);
host.Open();
Console.ReadLine();
}
}
[ServiceContract]
public interface ICalculator
{
[OperationContract]
int Add(int value1, int value2);
[OperationContract(AsyncPattern = true)]
IAsyncResult BeginAdd(int value1, int value2, AsyncCallback callback, object state);
int EndAdd(IAsyncResult result);
}
public class Calculator : ICalculator
{
public int Add(int value1, int value2)
{
Console.WriteLine(
"Incoming Add request {0}, {1}",
value1.ToString(),
value2.ToString());
System.Threading.Thread.Sleep(500);
return value1 + value2;
}
public IAsyncResult BeginAdd(int value1, int value2, AsyncCallback callback, object state)
{
throw new NotImplementedException();
}
public int EndAdd(IAsyncResult result)
{
throw new NotImplementedException();
}
}
}
Client:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
using Service;
namespace Client
{
class Program
{
static void Main(string[] args)
{
NetTcpBinding netBinding = new NetTcpBinding();
ChannelFactory<ICalculator> factory = new ChannelFactory<ICalculator>(
netBinding,
"net.tcp://localhost:61501/Calulator");
for (int i = 0; i < 100; i++)
{
ICalculator service = factory.CreateChannel();
service.BeginAdd(i, 0, SaveCallback, service);
}
Console.ReadLine();
}
static void SaveCallback(IAsyncResult ar)
{
ICalculator service = (ICalculator)ar.AsyncState;
Console.WriteLine(service.EndAdd(ar).ToString());
((IContextChannel)service).Close();
}
}
}
I think that you have encountered windows (7/Vista/XP) tcp connection limit. These OS:s limit the number of (inbound) tcp connections to 20. Googling "windows tcp connection limit" gives you a lot of more information about the subject.
SuperUser has also a thread about this subject: https://superuser.com/questions/253141/inbound-tcp-connection-limit-in-windows-7. More information also in ServerFault (linked from SU).
So to answer your question:
No, you should not open new channel for each call.
You hit the limit of non-server version of windows (probably 7 or Vista because you can get 20 connections). You get random number of successful connections because some times the OS is quick enough to clean few first connections, but the request rate is so high, that you are going to hit the maximum eventually.
I am trying to build a small WCF service and wanted to utilize it in a test application.
PFB service code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace HelloIndigo
{
[ServiceContract(Namespace="http://www.thatindigoirl.com/samples/2006/06")]
public interface IHelloIndigoService
{
[OperationContract]
string HelloIndigo();
}
public class HelloIndigoService : IHelloIndigoService
{
public string HelloIndigo()
{
return "Hello indigo";
}
}
}
Host Code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
namespace Host
{
class Program
{
static void Main(string[] args)
{
using (ServiceHost host = new ServiceHost(typeof(HelloIndigo.HelloIndigoService), new Uri("http://localhost:8000/HelloIndigo")))
{
host.AddServiceEndpoint(typeof(HelloIndigo.IHelloIndigoService), new BasicHttpBinding(), #"HelloIndigoService");
host.Open();
Console.WriteLine("Press <ENTER> to terminate the service hosy");
Console.ReadLine();
}
}
}
}
Whenever I am trying to run Host I am getting below mentioned error in host.Open() statement.
HTTP could not register URL
http://+:8000/HelloIndigo/. Your
process does not have access rights to
this namespace (see
http://go.microsoft.com/fwlink/?LinkId=70353
for details).
Can anyone help me with this
You need to run the host app with elevated privileges (i.e., "As Administrator"). Under Vista/Win7, only administrative accounts have the permission to register socket listeners.
First question is, how can I get the type of an object stored in a variable? Generally we do:
Type t = typeof(ClassName); //if I know the class
but, how can I say something:
Type t = typeof(varClassName); //if the class name is stored in a variable
Second question, a broader picture is, I have a WCF service that contains a DataContract class say "MyClass" and I have defined a custom attribute called "MyAttribute" to it. There is one method say "GetDataUsingDataContract" with a parameter of type MyClass. Now on client, I invoke the webservice. I use MethodInfo and ParameterInfo classes to get the parameters of the method in question. But how can I access the attributes of the method parameter which is actually a class Myclass? Here is the code that I tried:
MyService.Service1Client client = new MyService.Service1Client();
Type t = typeof(MyService.Service1Client);
MethodInfo members = t.GetMethod("GetDataUsingDataContract");
ParameterInfo[] parameters = members.GetParameters();
foreach (var parameter in parameters)
{
MemberInfo mi = parameter.ParameterType; //Not sure if this the way
object[] attributes;
attributes = mi.GetCustomAttributes(true);
}
Above code doesn't retrieve me the custom attribute "MyAttribute". I tried the concept in the class that is defined in the same project and it works. Please HELP!
but, how can I say something:
Type t = typeof(varClassName); //if the class name is stored in a variable
Try
Type.GetType("varClassName", false, true);
As to your second question:
Above code doesn't retrieve me the
custom attribute "MyAttribute". I
tried the concept in the class that is
defined in the same project and it
works. Please HELP!
Just guessing, I'm not sure that attributes are exposed to the client, by default. I think its the same issue as an untrusted assembly. Some attributes are sensitive info. See this:
http://blogs.msdn.com/b/haibo_luo/archive/2006/02/21/536470.aspx
But you could try linking the service project types into your app by first referencing the service assembly in your client project, then going to your service reference -> "Configure Service Reference" and selecting "Reuse types in all referenced assemblies". I'm not sure this option will affect the service interface classes, but I use it often with my domain objects. Worth a try.
Type mi = parameter.ParameterType; //Not sure if this the way
object[] attributes;
attributes = mi.GetCustomAttributes(true);
Ensure your proxy class has knowledge on attributes
Hope this will help
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 System.Runtime.Serialization;
using System.Reflection;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
StartService();
}
string url = "http://localhost:234/MyService/";
private void StartClient()
{
IMyService myService = ChannelFactory<IMyService>.CreateChannel(new BasicHttpBinding(), new EndpointAddress(url));
Type t = typeof(IMyService);
MethodInfo members = t.GetMethod("MyMethod");
ParameterInfo[] parameters = members.GetParameters();
foreach (var parameter in parameters)
{
Type mi = parameter.ParameterType;
object[] attributes;
attributes = mi.GetCustomAttributes(true);
}
}
private void StartService()
{
ServiceHost host = new ServiceHost(typeof(MyService), new Uri(url));
host.AddServiceEndpoint(typeof(IMyService), new BasicHttpBinding(), "");
host.Open();
}
private void button1_Click(object sender, EventArgs e)
{
StartClient();
}
}
[AttributeUsage(AttributeTargets.Interface)]
public class MyAttrib : Attribute
{
}
[MyAttrib]
public interface IMyContract
{
string Name { get; set; }
}
[DataContract]
public class MyContract : IMyContract
{
[DataMember]
public string Name { get; set; }
}
[ServiceContract]
public interface IMyService
{
[OperationContract]
bool MyMethod(IMyContract dummy);
}
[ServiceBehavior(UseSynchronizationContext = false)]
public class MyService : IMyService
{
public bool MyMethod(IMyContract dummy)
{
return true;
}
}
}
I'm new to Silverlight and WCF services. I'm trying to write a client application that can manipulate an object server side.
My problem is that each time my Silverlight client makes a call to the service, it enters into the constructor systematically
public SilverLightEnabledWcfService()
{
}
In the below example, I simply want to increment or decrement a number depending on the activity client side.
How am I supposed to do this properly?
I also tried to create a regular ASP.net client page and I got the same result, ie the server doesn't remember the session. So I don't think the problem is in my client, but I'm still happy to post the code if it helps.
Thanks !!
using System;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Activation;
using Count.Library;
namespace Count.WebApp
{
[ServiceContract(Namespace = "")]
[ServiceBehavior(InstanceContextMode = InstanceContextMode.PerSession)]
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
public class SilverLightEnabledWcfService
{
public SilverLightEnabledWcfService()
{
}
private Class1 _class1;
[OperationContract]
public int Add1()
{
if (_class1 == null)
_class1 = new Class1(0);
_class1.Add1();
return Value;
}
[OperationContract]
public int Remove1()
{
if (_class1 == null)
_class1 = new Class1(0);
_class1.Remove1();
return Value;
}
public int Value
{
get
{
return _class1.Count;
}
}
}
}
Sessions require the wsHttpBinding, but this is not supported by Silverlight. There are workarounds, though:
http://web-snippets.blogspot.com/2008_08_01_archive.html
http://forums.silverlight.net/forums/t/14130.aspx
This is a fully functional WCF Hello World program. I.e. I am able to run this program without any Exception.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
namespace DataContractsNamespace
{
[DataContract]
public class AccountInfo
{
[DataMember]
public string FirstName { get; set; }
[DataMember]
public string LastName { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
namespace Clients
{
public class BankProxy : ServiceContractsNamespace.IBank
{
ServiceContractsNamespace.IBank channel;
public BankProxy()
{
channel = ChannelFactory<ServiceContractsNamespace.IBank>.CreateChannel(new BasicHttpBinding(), new EndpointAddress("http://localhost:8000/Services/BankService"));
}
public decimal GetAcccountBalance(string AcctNo)
{
return channel.GetAcccountBalance(AcctNo);
}
public DataContractsNamespace.AccountInfo GetAccountInfo(string AcctNo)
{
return channel.GetAccountInfo(AcctNo);
}
}
}
using System;
using System.Collections.Generic;
using System.ServiceModel;
using System.Text;
namespace ServiceContractsNamespace
{
[ServiceContract]
public interface IBank
{
[OperationContract]
decimal GetAcccountBalance(string AcctNo);
[OperationContract]
DataContractsNamespace.AccountInfo GetAccountInfo(string AcctNo);
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Clients
{
class Program
{
static void Main(string[] args)
{
BankProxy prox = new BankProxy();
Console.WriteLine("Hit enter to invoke the service call. Type exit then enter to close");
while (Console.ReadLine() != "exit")
{
string balance = prox.GetAcccountBalance("1234").ToString("c");
DataContractsNamespace.AccountInfo ai = prox.GetAccountInfo("1234");
Console.WriteLine("{0} {1} your account balance is {2}.", ai.FirstName, ai.LastName, balance);
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Hosts
{
public class BankService : ServiceContractsNamespace.IBank
{
public decimal GetAcccountBalance(string AcctNo)
{
return 1.37m;
}
public DataContractsNamespace.AccountInfo GetAccountInfo(string AcctNo)
{
DataContractsNamespace.AccountInfo ai = new DataContractsNamespace.AccountInfo();
ai.FirstName = "Paul";
ai.LastName = "Johansen";
return ai;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel;
using System.Text;
namespace Hosts
{
class Program
{
static void Main(string[] args)
{
ServiceHost servHo = new ServiceHost(typeof(BankService), new Uri("http://localhost:8000/Services"));
servHo.AddServiceEndpoint(typeof(ServiceContractsNamespace.IBank), new BasicHttpBinding(), "BankService");
servHo.Open();
Console.WriteLine("This service is open for business. Hit Enter to close.");
Console.ReadLine();
servHo.Close();
}
}
}
As you can see, AccountInfo - Data contract is shared by both Client and Host.
I need to keep data contract only to Host/Service side.
Clients should only see interfaces of DataContracts (like IAccountInfo).
How should I modify my program to introduce IAccountInfo?
It sounds like you want to return an interface instead of a class. I'm not exactly sure why you are not content to return AccountInfo. However, you should be able to do this but you will need to use a KnownType or perhaps ServiceKnownType to make it work.
Alternately, if you are working in a fully .NET environment you can use the NetDataContractSerializer instead of the DataContractSerializer.
For reference and examples you can check out:
http://nirajrules.wordpress.com/2009/08/26/wcf-serializers-xmlserializer-vs-datacontratserializer-vs-netdatacontractserializer/
http://www.pluralsight.com/community/blogs/aaron/archive/2006/04/21/22284.aspx
http://weblogs.asp.net/avnerk/archive/2006/07/31/WCF-Serialization-part-1_3A00_-Interfaces_2C00_-Base-classes-and-the-NetDataContractFormatSerializer.aspx
http://www.thoughtshapes.com/WCF/ExampleTwo.htm
And what should IBank.GetAccountInfo return to client if you don't want to share AccountInfo? create 2 classes make the first datacontract the second not, and where you want to share use the first one, where not, the second one